001 /*
002 * Copyright (c) 2005 Stephen J. McConnell
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.lang;
020
021 import java.io.IOException;
022 import java.util.ArrayList;
023 import java.net.URL;
024 import java.net.URI;
025 import java.net.URLClassLoader;
026
027 import net.dpml.transit.Artifact;
028
029 import net.dpml.util.Logger;
030
031 /**
032 * A named classloader.
033 *
034 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
035 * @version 1.0.2
036 */
037 public class StandardClassLoader extends URLClassLoader
038 {
039 //--------------------------------------------------------------------
040 // static
041 //--------------------------------------------------------------------
042
043 /**
044 * Internal utility class to build a classloader. If the supplied url
045 * sequence is zero length the parent classloader is returned directly.
046 *
047 * @param logger the logging channel
048 * @param name the name identifying the classloader
049 * @param category the category that this classloader is handling
050 * @param parent the parent classloader
051 * @param uris the uris to assign as classloader content
052 * @return the classloader
053 * @exception IOException if an I/O error occurs
054 */
055 public static ClassLoader buildClassLoader( Logger logger, String name, Category category, ClassLoader parent, URI[] uris )
056 throws IOException
057 {
058 URL[] urls = toURLs( uris );
059 if( 0 == urls.length )
060 {
061 return parent;
062 }
063 ArrayList list = new ArrayList();
064 for( int i=0; i < urls.length; i++ )
065 {
066 if( isaCandidate( parent, urls[i] ) )
067 {
068 list.add( urls[i] );
069 }
070 }
071 URL[] qualified = (URL[]) list.toArray( new URL[0] );
072 if( qualified.length == 0 )
073 {
074 return parent;
075 }
076 else
077 {
078 ClassLoader loader =
079 new StandardClassLoader( name, category, qualified, parent );
080 classloaderConstructed( logger, name, category, loader );
081 return loader;
082 }
083 }
084
085 /**
086 * Convert a sequence of URIs to URLs.
087 * @param uris the uris to convert
088 * @return the corresponding urls
089 * @exception IOException of a transformation error occurs
090 */
091 public static URL[] toURLs( URI[] uris ) throws IOException
092 {
093 URL[] urls = new URL[ uris.length ];
094 for( int i=0; i < urls.length; i++ )
095 {
096 URI uri = uris[i];
097 if( Artifact.isRecognized( uri ) )
098 {
099 urls[i] = Artifact.toURL( uri );
100 }
101 else
102 {
103 urls[i] = uri.toURL();
104 }
105 }
106 return urls;
107 }
108
109 /**
110 * Test if the supplied url is already present within the supplied classloader.
111 * @param classloader the classloader to validate against
112 * @param url to url to check for
113 * @return true if the url is not included in the classloader
114 */
115 private static boolean isaCandidate( ClassLoader classloader, URL url )
116 {
117 if( classloader instanceof URLClassLoader )
118 {
119 URL[] urls = ( (URLClassLoader) classloader ).getURLs();
120 for( int i=0; i < urls.length; i++ )
121 {
122 if( urls[i].equals( url ) )
123 {
124 return false;
125 }
126 }
127 ClassLoader parent = classloader.getParent();
128 if( parent == null )
129 {
130 return true;
131 }
132 else
133 {
134 return isaCandidate( parent, url );
135 }
136 }
137 else
138 {
139 return true;
140 }
141 }
142
143 //--------------------------------------------------------------------
144 // state
145 //--------------------------------------------------------------------
146
147 private final Category m_category;
148 private final String m_name;
149
150 //--------------------------------------------------------------------
151 // constructor
152 //--------------------------------------------------------------------
153
154 /**
155 * Creation of a new classloader.
156 * @param name a name identifying the plugin
157 * @param category the classloader category identifier
158 * @param urls an array of urls to add to the classloader
159 * @param parent the parent classloader
160 */
161 public StandardClassLoader( String name, Category category, URL[] urls, ClassLoader parent )
162 {
163 super( urls, parent );
164 m_category = category;
165 m_name = name;
166 }
167
168 //--------------------------------------------------------------------
169 // StandardClassLoader
170 //--------------------------------------------------------------------
171
172 /**
173 * Return the classloader category
174 * @return the classloader category
175 */
176 public Category getCategory()
177 {
178 return m_category;
179 }
180
181 /**
182 * Return a string representation of the classloader.
183 * @return the string value
184 */
185 public String getAnnotations()
186 {
187 StringBuffer buffer = new StringBuffer();
188 ClassLoader parent = getParent();
189 if( parent instanceof URLClassLoader )
190 {
191 URLClassLoader urlClassLoader = (URLClassLoader) parent;
192 buffer.append( getURLClassLoaderAnnotations( urlClassLoader ) );
193 }
194 buffer.append( " " );
195 URL[] urls = getURLs();
196 for( int i=0; i<urls.length; i++ )
197 {
198 String path = urls[i].toString();
199 if( !path.startsWith( "file:" ) )
200 {
201 buffer.append( path );
202 buffer.append( " " );
203 }
204 }
205 return buffer.toString().trim();
206 }
207
208 private String getURLClassLoaderAnnotations( URLClassLoader classloader )
209 {
210 StringBuffer buffer = new StringBuffer();
211 ClassLoader parent = classloader.getParent();
212 if( ( null != parent ) && ( parent instanceof URLClassLoader ) )
213 {
214 URLClassLoader urlClassLoader = (URLClassLoader) parent;
215 buffer.append( getURLClassLoaderAnnotations( urlClassLoader ) );
216 }
217 if( ClassLoader.getSystemClassLoader() == classloader )
218 {
219 return "";
220 }
221 buffer.append( " " );
222 URL[] urls = classloader.getURLs();
223 for( int i=0; i<urls.length; i++ )
224 {
225 String path = urls[i].toString();
226 if( !path.startsWith( "file:" ) )
227 {
228 buffer.append( path );
229 buffer.append( " " );
230 }
231 }
232 return buffer.toString().trim();
233 }
234
235 /**
236 * Return a string representing of the classloader.
237 * @param expanded if true return an expanded representation of the classloader
238 * @return the string representation
239 */
240 public String toString( boolean expanded )
241 {
242 StringBuffer buffer = new StringBuffer();
243 listClasspath( buffer );
244 return buffer.toString();
245 }
246
247 //--------------------------------------------------------------------
248 // Object
249 //--------------------------------------------------------------------
250
251 /**
252 * Return a string representing of the classloader.
253 * @return the string representation
254 */
255 public String toString()
256 {
257 final String label =
258 getClass().getName()
259 + "#"
260 + System.identityHashCode( this );
261 return label;
262 }
263
264 /**
265 * Internal operation to list the classloader classpath.
266 * @param buffer the buffer to list to
267 */
268 protected void listClasspath( StringBuffer buffer )
269 {
270 listClasspath( buffer, this );
271 buffer.append( "\n" );
272 }
273
274 /**
275 * Internal operation to list a classloader classpath.
276 * @param buffer the buffer to list to
277 * @param classloader the classloader to list
278 */
279 protected void listClasspath( StringBuffer buffer, ClassLoader classloader )
280 {
281 String label =
282 "\nClassLoader: "
283 + classloader.getClass().getName()
284 + " ("
285 + System.identityHashCode( classloader )
286 + ")";
287
288 if( classloader instanceof StandardClassLoader )
289 {
290 StandardClassLoader cl = (StandardClassLoader) classloader;
291 ClassLoader parent = cl.getParent();
292 if( null != parent )
293 {
294 listClasspath( buffer, parent );
295 }
296
297 if( null != m_name )
298 {
299 label = label.concat( "\nLabel: " + cl.m_name + " " + cl.getCategory() );
300 }
301 else
302 {
303 label = label.concat( "\nCategory: " + cl.getCategory() );
304 }
305 buffer.append( label );
306 buffer.append( "\n" );
307 appendEntries( buffer, cl );
308 }
309 else if( classloader instanceof URLClassLoader )
310 {
311 URLClassLoader cl = (URLClassLoader) classloader;
312 ClassLoader parent = cl.getParent();
313 if( null != parent )
314 {
315 listClasspath( buffer, parent );
316 }
317 buffer.append( label );
318 appendEntries( buffer, cl );
319 }
320 else
321 {
322 buffer.append( label );
323 buffer.append( "]\n" );
324 }
325 }
326
327 private static void appendEntries( StringBuffer buffer, URLClassLoader classloader )
328 {
329 URL[] urls = classloader.getURLs();
330 for( int i=0; i < urls.length; i++ )
331 {
332 buffer.append( "\n " );
333 URL url = urls[i];
334 String spec = url.toString();
335 buffer.append( spec );
336 }
337 buffer.append( "\n" );
338 }
339
340 /**
341 * Return a string representing a report fo the common classloader chain
342 * following by the primary annd seciondarty classloaders.
343 * @param primary the primary classloader
344 * @param secondary the secondary classloader
345 * @return the report
346 */
347 public static String toString( ClassLoader primary, ClassLoader secondary )
348 {
349 StringBuffer buffer = new StringBuffer();
350 ClassLoader anchor = getCommonParent( primary, secondary );
351 if( null != anchor )
352 {
353 buffer.append( "\n----------------------------------------------------------------" );
354 buffer.append( "\nCommon Classloader" );
355 buffer.append( "\n----------------------------------------------------------------" );
356 list( buffer, anchor );
357 }
358 buffer.append( "\n----------------------------------------------------------------" );
359 buffer.append( "\nPrimary Classloader" );
360 buffer.append( "\n----------------------------------------------------------------" );
361 list( buffer, primary, anchor );
362 buffer.append( "\n----------------------------------------------------------------" );
363 buffer.append( "\nSecondary Classloader" );
364 buffer.append( "\n----------------------------------------------------------------" );
365 list( buffer, secondary, anchor );
366 buffer.append( "\n----------------------------------------------------------------" );
367 return buffer.toString();
368 }
369
370 private static ClassLoader getCommonParent( ClassLoader primary, ClassLoader secondary )
371 {
372 ClassLoader[] primaryChain = getClassLoaderChain( primary );
373 ClassLoader[] secondaryChain = getClassLoaderChain( secondary );
374 return getCommonClassLoader( primaryChain, secondaryChain );
375 }
376
377 private static ClassLoader[] getClassLoaderChain( ClassLoader classloader )
378 {
379 if( null == classloader )
380 {
381 return new ClassLoader[0];
382 }
383 else
384 {
385 ArrayList list = new ArrayList();
386 list.add( classloader );
387 ClassLoader parent = classloader.getParent();
388 while( null != parent )
389 {
390 list.add( parent );
391 parent = parent.getParent();
392 }
393 ArrayList result = new ArrayList();
394 int n = list.size() - 1;
395 for( int i=n; i>-1; i-- )
396 {
397 result.add( list.get( i ) );
398 }
399 return (ClassLoader[]) result.toArray( new ClassLoader[0] );
400 }
401 }
402
403 private static ClassLoader getCommonClassLoader( ClassLoader[] primary, ClassLoader[] secondary )
404 {
405 ClassLoader anchor = null;
406 for( int i=0; i<primary.length; i++ )
407 {
408 ClassLoader classloader = primary[i];
409 if( secondary.length > i )
410 {
411 ClassLoader cl = secondary[i];
412 if( classloader == cl )
413 {
414 anchor = cl;
415 }
416 else
417 {
418 return anchor;
419 }
420 }
421 else
422 {
423 return anchor;
424 }
425 }
426 return anchor;
427 }
428
429 private static void list( StringBuffer buffer, ClassLoader classloader )
430 {
431 list( buffer, classloader, null );
432 }
433
434 private static void list( StringBuffer buffer, ClassLoader classloader, ClassLoader anchor )
435 {
436 if( classloader == anchor )
437 {
438 return;
439 }
440 ClassLoader parent = classloader.getParent();
441 if( null != parent )
442 {
443 list( buffer, parent, anchor );
444 }
445 String label =
446 "\nClassLoader: "
447 + classloader.getClass().getName()
448 + " (" + System.identityHashCode( classloader ) + ")";
449 buffer.append( label );
450 if( classloader instanceof StandardClassLoader )
451 {
452 StandardClassLoader loader = (StandardClassLoader) classloader;
453 if( null != loader.m_name )
454 {
455 buffer.append( "\nLabel: " + loader.m_name + " " + loader.m_category );
456 }
457 else
458 {
459 buffer.append( "\nCategory: " + loader.m_category );
460 }
461 }
462 if( classloader instanceof URLClassLoader )
463 {
464 URLClassLoader urlcl = (URLClassLoader) classloader;
465 buffer.append( "\n" );
466 appendEntries( buffer, urlcl );
467 }
468 }
469
470 /**
471 * Handle notification of the creation of a new classloader.
472 * @param logger the logging channel
473 * @param label the classloader label
474 * @param category the classloader category
475 * @param classloader the new classloader to report
476 */
477 private static void classloaderConstructed( Logger logger, String label, Category category, ClassLoader classloader )
478 {
479 if( logger.isTraceEnabled() )
480 {
481 int id = System.identityHashCode( classloader );
482 StringBuffer buffer = new StringBuffer();
483 buffer.append( "new " );
484 buffer.append( category.toString() );
485 buffer.append( " classloader for " + label );
486 buffer.append( "\n id: " + id );
487 ClassLoader parent = classloader.getParent();
488 if( null != parent )
489 {
490 int pid = System.identityHashCode( parent );
491 buffer.append(
492 "\n extends: "
493 + pid );
494 }
495 if( classloader instanceof URLClassLoader )
496 {
497 URLClassLoader loader = (URLClassLoader) classloader;
498 URL[] urls = loader.getURLs();
499 if( urls.length == 1 )
500 {
501 buffer.append(
502 "\n contains: 1 entry" );
503 }
504 else
505 {
506 buffer.append(
507 "\n contains: "
508 + urls.length
509 + " entries" );
510 }
511 for( int i=0; i < urls.length; i++ )
512 {
513 URL url = urls[i];
514 buffer.append(
515 "\n ["
516 + ( i+1 )
517 + "] "
518 + url.toString() );
519 }
520 }
521 logger.trace( buffer.toString() );
522 }
523 }
524 }